#!/usr/bin/env python2.6
# -*- coding: utf-8 -*-
""" Interface con el firmware del bootloader: funciones que se comunican 
    directamente con el bootloader en el dispositivo.
"""
import ctypes as c
from bl_interface import *
from usb_interface import *
import time

SI_SUCCESS = 0

## USBXpress Read/Write Timeouts (in ms)
USBX_READ_TIMEOUT   = 500
USBX_WRITE_TIMEOUT  = 500


def GetDeviceInfo (deviceHandle):
    """ Obtiene el bloque de información de datos del dispositivo
    """
    global api
    fnRetVal = True
    devInfo=DEVICE_INFO_BLOCK

    if deviceHandle != 0:
        code = c.c_char(chr(CMD_GET_DEVICE_INFO))
        numBytesWritten = c.c_uint32(0)
        if api.SI_Write(deviceHandle, c.byref(code), 1, c.byref(numBytesWritten))== 0:
            numBytesInQueue = c.c_uint32(0)
            queueStatus = c.c_uint32(0)
            start_time = time.clock()
            while (numBytesInQueue.value < DEVICE_INFO_BLOCK_RET_LENGTH) and (time.clock() - start_time < USBX_CHECKRXQUEUE_TIMEOUT):
                if (api.SI_CheckRXQueue(deviceHandle, c.byref(numBytesInQueue), c.byref(queueStatus)) != 0):
                    ##  MessageBox(NULL, "Error Checking Queue", "Error", MB_OK);
                    fnRetVal = false;
                    break;
            numBytesRead = c.c_uint32(0);
            if fnRetVal and (numBytesInQueue.value >= DEVICE_INFO_BLOCK_RET_LENGTH) :
                temp_buf = c.c_buffer(DEVICE_INFO_BLOCK_RET_LENGTH*2)
                if api.SI_Read(deviceHandle, temp_buf, numBytesInQueue, c.byref(numBytesRead)) == 0:
                    off=numBytesRead.value-DEVICE_INFO_BLOCK_RET_LENGTH
                    devInfo.BL_FW_ver_high = ord(temp_buf[off])
                    devInfo.BL_FW_ver_low = ord(temp_buf[off+1])
                    devInfo.USBX_FWLib_ver_high = ord(temp_buf[off+2])
                    devInfo.USBX_FWLib_ver_low = ord(temp_buf[off+3])
                    devInfo.App_FW_ver_high = ord(temp_buf[off+4])
                    devInfo.App_FW_ver_low = ord(temp_buf[off+5])
                    devInfo.signature = ord(temp_buf[off+6])*256+ord(temp_buf[off+7])
                    devInfo.devCode = ord(temp_buf[off+8])
                else:
                    ## MessageBox(NULL, "Error Reading Data", "Error", MB_OK);
                    fnRetVal = False
            else:
                ## MessageBox(NULL, "Error Sending Code", "Error", MB_OK);
                fnRetVal = False
        else:
            ## MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK);
            fnRetVal = False
    return fnRetVal,devInfo


def ResetDevice (deviceHandle):
    """ Resets the device (device performs a software reset)
    """
    global api
    if deviceHandle == INVALID_HANDLE_VALUE:
        # MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK)
        return False

    code = c.c_char(chr(CMD_SW_RESET))
    numBytesWritten = c.c_uint32(0)
    if api.SI_Write(deviceHandle, c.byref(code), 1, c.byref(numBytesWritten)) != 0:
        # MessageBox(NULL, "Error Sending Code", "Error", MB_OK);
        return False
    return True


def SetPage(deviceHandle, page):
    """ Sets the current page on the device. ErasePage, WritePage, and CRConPage
        functions will operate on this current page.
    """
    global api
    code = c.c_buffer(chr(CMD_SET_PAGE)+chr(page),2)
    numBytesWritten = c.c_uint32(0)

    if (deviceHandle == INVALID_HANDLE_VALUE):
        # MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK);
        return False
    
    # Write a 0x01 to the device to set the current flash page
    if (api.SI_Write(deviceHandle, c.byref(code), 2, c.byref(numBytesWritten)) != 0):
        # MessageBox(NULL, "Error Sending Code", "Error", MB_OK);
        return False

    status = ord(WaitForRead(deviceHandle, 1))

    if (status != COMMAND_OK):
        # MessageBox(NULL, "Device Failed to Set Page", "Error", MB_OK);
        return False
    else:
        return True


def ErasePage(deviceHandle, page):
    """ Erases the current page; Current page is set by SetPage.
    """
    global api
    code=c.c_char(chr(CMD_ERASE_PAGE))
    numBytesWritten=c.c_uint32(0)
    fnRetVal=True
    if (deviceHandle != INVALID_HANDLE_VALUE):
        # Set the current page
        if (SetPage(deviceHandle, page)):
            print 'Page set to',page
        # Write the command to erase the current page
            if (api.SI_Write(deviceHandle, c.byref(code), 1, c.byref(numBytesWritten)) == 0):
                numBytesInQueue = c.c_uint32(0)
                queueStatus = c.c_uint32(0)
                print "Written code for ErasePage command"
                start_time = time.clock()
                # Wait til we have data in the queue
                while ((numBytesInQueue.value < 1) and ((time.clock() - start_time) < USBX_CHECKRXQUEUE_TIMEOUT)):
                    if (api.SI_CheckRXQueue(deviceHandle, c.byref(numBytesInQueue), c.byref(queueStatus)) != 0):
                        # MessageBox(NULL, "Error Checking Queue", "Error", MB_OK);
                        fnRetVal = False
                print "Checked queue (ErasePage)"
                status = c.c_byte(COMMAND_FAILED)
                numBytesRead = c.c_uint32(0)
                if fnRetVal and (numBytesInQueue.value >= 1):
                    # Read the status of the erase
                    if (api.SI_Read(deviceHandle, c.byref(status), 1, c.byref(numBytesRead)) == 0):
                        print "Read status (ErasePage)"
                        if (status.value != COMMAND_OK):
                            # MessageBox(NULL, "Device Failed to Erase Page", "Error", MB_OK);
                            fnRetVal = False
                else:
                    # MessageBox(NULL, "Error Reading Data", "Error", MB_OK);
                    fnRetVal = False
            else:
                # MessageBox(NULL, "Error Sending Code", "Error", MB_OK);
                fnRetVal = False
        else:
            # MessageBox(NULL, "Error Setting Page", "Error", MB_OK);
            fnRetVal = False
    else:
        # MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK);
        fnRetVal = False
    return fnRetVal


def WritePage(deviceHandle, data, page, pageCRC):
    """ Writes the current page and retrievs the CRC of the page; 
        Current page is set by SetPage.
    """
    fnRetVal = True
    ## 1. Erase page (this command first sets the page).
    ## 2. Send Write Page command with first half (pageHalf=0) of the page data.
    ## 3. Wait and receive ack from device. This indicates that the device has received the data.
    ## 4. Wait and receive 2nd ack from device. This indicates that the device has written the data to flash.
    ## 5. Send Write Page command with second half (pageHalf=1) of the page data.
    ## 6. Wait and receive ack from device. This indicates that the device has received the data.
    ## 7. Wait and receive 2nd ack from device. This indicates that the device has written the data to flash.
    ## 8. Wait and receive 2-byte CRC of page from device.
    
    ## Erase the current page
    ## if_6
    global api
    if ErasePage(deviceHandle, page):
        # si toda la pagina es 0xFF, no hay nada que escribir
        escribo_algo=False
        for i in range(PAGE_SIZE):
            if data[page*PAGE_SIZE+i]!=0xFF:
                escribo_algo=True
                break
        if escribo_algo:
            for pageHalf in range(2):
                code=c.c_buffer(PAGE_SIZE + 2)
                numBytesWritten = c.c_uint32(0)
                code[0] = chr(CMD_WRITE_PAGE)
                code[1] = chr(pageHalf)
                ## Loop through PAGE_SIZE/2 bytes worth of data
                ## and write the new byte
                for i in range(PAGE_SIZE/2):
                    code[i+2] = chr(data[(page * PAGE_SIZE) + i + pageHalf*(PAGE_SIZE/2)])

                ## Write [0x03, [pageHalf], and [DATA]] to the device
                ## if_5
                if api.SI_Write(deviceHandle, c.byref(code), (PAGE_SIZE/2) + 2, c.byref(numBytesWritten)) == 0:
                    numBytesInQueue = c.c_uint32(0)
                    queueStatus = c.c_uint32(0)
                    print "Written code for WritePage command"
                    start_time=time.clock()

                   ## Wait til we have data in the queue
                    while (numBytesInQueue.value < 1) and ((time.clock() - start_time)*1000 < USBX_CHECKRXQUEUE_TIMEOUT ):
                        if (api.SI_CheckRXQueue(deviceHandle, c.byref(numBytesInQueue), c.byref(queueStatus)) != 0):
                            # MessageBox(NULL, "Error Checking Queue", "Error", MB_OK);
                            fnRetVal = False
                    print "Checked queue for initial ack (WritePage)"
                    print "Bytes en cola",numBytesInQueue.value
                    print "Satus de cola",queueStatus.value
                    #raw_input("ack1 ok: enter para seguir")
                    
                    #buff=c.c_buffer(64)
                    status = c.c_byte(COMMAND_FAILED)
                    #buff[0]=chr(COMMAND_FAILED)
                    numBytesRead = c.c_uint32(numBytesInQueue.value)
                    ## if_4
                    if fnRetVal and (numBytesInQueue.value >= 1):
                        # Read the success of the command
                        # if_3
                        if api.SI_Read(deviceHandle, c.byref(status), 1, c.byref(numBytesRead)) == 0:
                            print "Read status of initial ack (WritePage)"
                            if status.value == COMMAND_OK: ## if_2
                                numBytesInQueue.value=0
                                status.value = COMMAND_FAILED
                                start_time = time.clock()
                                ## Wait til we have data in the queue
                                while ((numBytesInQueue.value < 1) and ((time.clock() - start_time)*1000 < USBX_CHECKRXQUEUE_TIMEOUT)):
                                    if (api.SI_CheckRXQueue(deviceHandle, c.byref(numBytesInQueue), c.byref(queueStatus)) != 0):
                                        # MessageBox(NULL, "Error Checking Queue", "Error", MB_OK);
                                        fnRetVal = False
                                print "Checked queue again for write complete (WritePage)"
                                numBytesInQueue.value=1
                                if fnRetVal and (numBytesInQueue.value >= 1):
                                    ## Read the success of the write
                                    status.value=COMMAND_FAILED
                                    if api.SI_Read(deviceHandle, c.byref(status), 1, c.byref(numBytesRead)) != 0:
                                        fnRetVal=False
                                        print "No hay ack de write complete"
                                    else:
                                        print "Read status again for write complete (WritePage)",hex(status.value)
                                else:
                                    if fnRetVal:
                                        print "No hay rta del micro"
                                    else:
                                        print "No se puede ver la cola"
                                #raw_input("ack 2 ok: enter para seguir")
                                    
                                if pageHalf and fnRetVal and status.value == COMMAND_OK: ## if_1
                                    print "pagina completa",page
                                    print "crc=",hex(CheckCRC(data,page))
                                    numBytesInQueue.value = 0
                                    start_time = time.clock()
                                    while (numBytesInQueue.value < 2) and ((time.clock() - start_time)*1000 < USBX_CHECKRXQUEUE_TIMEOUT) :
                                        if (api.SI_CheckRXQueue(deviceHandle, c.byref(numBytesInQueue), c.byref(queueStatus)) != 0):
                                            # MessageBox(NULL, "Error Checking Queue", "Error", MB_OK);
                                            fnRetVal = False
                                    print "Checked queue for CRC (WritePage)"
                                    numBytesInQueue.value=2
                                    if fnRetVal and (numBytesInQueue.value >= 2):
                                        crc=c.c_buffer('\x00\x00',2)
                                        if api.SI_Read(deviceHandle, c.byref(crc), 2, c.byref(numBytesRead)) == 0:
                                            print "Read 2-byte CRC (WritePage)"
                                            ## Convert the firmware CRC
                                            pageCRC = ord(crc[0])*256 + ord(crc[1])
                                            print hex(pageCRC)
                                        else:
                                            # MessageBox(NULL, "Device Failed to read CRC at the end of page write", "Error", MB_OK);
                                            fnRetVal = False
                                            break   # force exit from for loop
                                    else:
                                        if fnRetVal:
                                            print "solo hay en cola",numBytesInQueue.value
                                        else:
                                            print "no se puede leer el estado de la cola"
                                else:
                                    if fnRetVal and status.value==COMMAND_OK:
                                        print "media página #",page
                                        #time.sleep(1)
                                    else: ## fi_1
                                        # MessageBox(NULL, "Device Failed to Write Page", "Error", MB_OK);
                                        fnRetVal = False
                                        break   # force exit from for loop
                            else: ## fi_2
                                # MessageBox(NULL, "Error Reading Data", "Error", MB_OK);
                                fnRetVal = False
                                break   # force exit from for loop
                        else: ## fi_3
                            # MessageBox(NULL, "Device Failed to Init Data For Page", "Error", MB_OK);
                            fnRetVal = False
                            break  # force exit from for loop
                    else: ## fi_4
                        # MessageBox(NULL, "Error Reading Data", "Error", MB_OK);
                        fnRetVal = False
                        break   # force exit from for loop
                else: # fi_5
                    # MessageBox(NULL, "Error Sending Code", "Error", MB_OK);
                    fnRetVal = False
                    break   # force exit from for loop
        else:
            print "Nada que escribir en página #",page
            
    else: ## fi_6
        # MessageBox(NULL, "Error Erasing Page " + (CString)itoa(page, buf, 10), "Error", MB_OK);
        fnRetVal = False
        
    return fnRetVal


def CheckCRCPage(deviceHandle, page):
    """ Retrieves the CRC of the current page; Current page is set by SetPage.
    """
    global api

    if (deviceHandle == INVALID_HANDLE_VALUE):
        # MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK);
        return None

    ## Set the current page to check CRC on
    if not SetPage(deviceHandle, page):
        # MessageBox(NULL, "Error Setting Page", "Error", MB_OK);
        return None

    # Write the 0x04 to get the current page's CRC
    code = c.c_char(chr(CMD_CRC_PAGE))
    numBytesWritten = c.c_uint32(0)
    if (api.SI_Write(deviceHandle, c.byref(code), 1, c.byref(numBytesWritten)) != 0):
        # MessageBox(NULL, "Error Sending Code", "Error", MB_OK);
        return None

    crc = WaitForRead(deviceHandle, 2)

    # Convert the firmware CRC
    pageCRC = ord(crc[0])*256 + ord(crc[1])
    print hex(pageCRC)
    return pageCRC
    
    
def SetFlashKeyCodes(deviceHandle, key0, key1):
    """ Sends the Flash key codes to the device; These key codes are required to
        erase or write Flash memory.
    """
    global api

    if (deviceHandle == INVALID_HANDLE_VALUE):
        # MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK);
        return False

    code = c.c_buffer(chr(CMD_SET_FLASH_KEYS)+chr(key0)+chr(key1),3)
    numBytesWritten = c.c_uint32(0)
    if (api.SI_Write(deviceHandle, c.byref(code), 3, c.byref(numBytesWritten)) != 0):
        # MessageBox(NULL, "Error Sending Code", "Error", MB_OK);
        return False

    status = ord(WaitForRead(deviceHandle, 1))

    if status != COMMAND_OK:
        return False
    else:
        return True


def WriteSignature (deviceHandle):
    """ Writes the 2-byte signature to the device. After the signature is
        written, the application firmware is ready to be executed.
    """
    global api
    code=c.c_buffer(chr(CMD_WRITE_SIGNATURE)+chr(REF_SIGNATURE & 0x00FF)+chr((REF_SIGNATURE&0xFF00)>> 8),3)
    numBytesWritten = c.c_uint32(0)

    if (deviceHandle == INVALID_HANDLE_VALUE):
        # MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK);
        return False

    if (api.SI_Write(deviceHandle, c.byref(code), 3, c.byref(numBytesWritten)) != 0):
        # MessageBox(NULL, "Error Sending Code", "Error", MB_OK);
        return False

    status = ord(WaitForRead(deviceHandle, 1))

    if (status == SIGNATURE_NOT_ERASED):
        # MessageBox(NULL, "Validation Signature should be erased before attempting to rewrite Signature", "Error", MB_OK);
        return False

    if (status != COMMAND_OK):
        # MessageBox(NULL, "Unknown error occured!", "Error", MB_OK);
        return False
    else:
        return True


def WriteFlash(deviceHandle, devInfo, ih):
    """ Escribe las páginas de flash del dispositivo.
        (Sólo aquellas que tengan algún byte distinto a 0xff)
    """
    global api
    # 1er paso: escribir las claves
    if not SetFlashKeyCodes(deviceHandle, FLASH_KEY0, FLASH_KEY1):
        print "Error al escribir los códigos de flash"
        return False
    print "Códigos de flash escritos"

    # 2do paso: Borrar la última pagina de usuario para limpiar
    # la firma de validación
    if not ErasePage(deviceHandle, APP_END_PAGE):
        print "Error borrando la pagina de validacion de firma"
        return False
    print "ultima pagina borrada (no hay firma valida)"

    # 3er paso: Borrar-escribrir-CRC de paginas de flash
    pageCRC=0
    for pag in range(APP_START_PAGE, APP_END_PAGE+1):
        if not WritePage(deviceHandle, ih, pag, pageCRC):
            print "Error escribiendo pagina", pag
            return False
        print "Borrada y escrita página #",pag
        pageCRC=CheckCRCPage(deviceHandle, pag)
        if pageCRC != CheckCRC(ih, pag):
            print "CRC distinto en pagina ",pag
            return False
        else:
            print "CRC Ok!"

    # 4to paso: Escribir firma
    if not WriteSignature(deviceHandle):
        print "Error escribiendo firma de validación"
        return False
    print "Firma escrita"

    api.SI_FlushBuffers(deviceHandle,0,0)

    # 5to paso: Leer y verificar firma
    res,devInfo=GetDeviceInfo(deviceHandle)
    if devInfo.signature!=REF_SIGNATURE:
        print "Firma de validación incorrecta"
        print hex(devInfo.signature)
        print hex(REF_SIGNATURE)
        return False
    else:
        print "Firma de validación correcta!"

    # 6to paso: Limpiar los códigos del flash
    if not SetFlashKeyCodes(deviceHandle, 0x00, 0x00):
        print "Error liberando los códigos de flash"
        return False
    print "Codigos de flash limpiados"
    return True


def CheckCRC(ih, page):
    """ Returns the CRC on a page in the software memory
    """
    crc=0
    # Process each byte in the page into the running CRC
    for i in range(PAGE_SIZE):
        crc=Poly(crc, ih[page*PAGE_SIZE+i])
    return crc

def Poly(crc, byte):
    """ Actualiza el crc con un nuevo byte usando el polinomio
        CRC16-CCITT FCS (X^16+X^12+X^5+1)
    """
    CRC_POLY=0x8408
    crc = crc ^byte
    for i in range(8):
        if crc&0x01:
            crc=crc>>1
            crc=crc ^ CRC_POLY
        else:
            crc = crc>>1
    return crc
    
def DeviceInBootloader (deviceNum):
    global api

    buff = c.c_buffer(64)
    opt = c.c_ulong(dev_str['SI_RETURN_SERIAL_NUMBER'])
    res = api.SI_GetProductString(deviceNum, c.byref(buff), opt)
    if res != SI_SUCCESS:
        raise("No se pudo leer descripcion de dispositivo", deviceNum)
    if buff.value[-1] == BL_MODE_SERIAL_STR_LAST_CHAR:
        return True
    else:
        return False
